package Calculator.Parser;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import java.util.regex.Matcher;

import Calculator.Exceptions.UnclosedParenthesisException;
import Calculator.Exceptions.WrongExpressionException;
import Calculator.Exceptions.WrongSymbolException;
import Calculator.Parser.Elements.Token;
import Calculator.Parser.Elements.TokenFactory;

public class InfixParser {

	private Queue<Token> output;
	private Stack<Token> operatorStack;
	private TokenFactory factory;
	
	public InfixParser() {
		this.factory = new TokenFactory();
		this.setOutput(new LinkedList<Token>());
		this.setOperatorStack(new Stack<Token>());
	}
	
	/**
	 * Parsea la expresión a Notación Polaca Inversa
	 * @param input Cálculo
	 * @return String en Notación Polaca Inversa
	 * @throws WrongSymbolException Si se encuentra un literal distinto a número, paréntesis u operador
	 * @throws UnclosedParenthesisException Si no se cierra un paréntesis
	 * @throws WrongExpressionException Si está mal redactado el cálculo. Por ejemplo si hay dos números seguidos
	 */
	public String parseToReversePolishNotation(String input) 
			throws WrongSymbolException, UnclosedParenthesisException, WrongExpressionException {
		this.checkExpressionSymbols(input);
		Matcher elementsMatcher = Utilities.elementsPattern.matcher(input);
		Token previous = null;
		while (elementsMatcher.find()){
			Token t = this.factory.createToken(elementsMatcher.group(1));
			if (previous != null){
				previous.checkCorrectSyntax(t);
			}
			t.performIteration(this);
			previous = t;
		}
		while (!this.operatorStack.empty()){
			Token operator = this.operatorStack.pop();
			if (!operator.isNumber() && !operator.isOperator()){
				throw new UnclosedParenthesisException();
			}
			this.output.add(operator);
		}
		return this.convertQueueToString();
	}

	/**
	 * Valida si el literal leído es correcto
	 * @param input
	 * @throws WrongSymbolException
	 */
	private void checkExpressionSymbols(String input) throws WrongSymbolException{
		Matcher wrongExpression = Utilities.wrongExpressionPattern.matcher(input);
		if (wrongExpression.find()){
			throw new WrongSymbolException(wrongExpression.group(1));
		}
	}

	/**
	 * Convierte a String los valores almacenados en la cola de salida
	 * @return
	 */
	private String convertQueueToString() {
		StringBuffer outputBuffer = new StringBuffer("");
		while (!this.output.isEmpty()){
			Token currentToken = this.output.remove();
			outputBuffer.append(currentToken.getId() + " ");
		}
		return outputBuffer.toString().trim();
	}

	public Stack<Token> getOperatorStack() {
		return operatorStack;
	}

	public void setOperatorStack(Stack<Token> operatorStack) {
		this.operatorStack = operatorStack;
	}

	public Queue<Token> getOutput() {
		return output;
	}

	public void setOutput(Queue<Token> output) {
		this.output = output;
	}

}
